package com.xlh.support.attendance.service.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.collect.*;
import com.xlh.aop.PreAcquireLock;
import com.xlh.common.ResultEnum;
import com.xlh.dao.conclude.helper.BusiFullMarkMapperHelper;
import com.xlh.dao.support.StatisticsMapper;
import com.xlh.dao.support.StatisticsMapperExt;
import com.xlh.dao.user.ext.UserMapperExt;
import com.xlh.dto.conclude.BusiFullMarkAttendanceDTO;
import com.xlh.dto.conclude.BusiFullMarkDTO;
import com.xlh.dto.user.ClassCourseDTO;
import com.xlh.dto.user.UserInfoDTO;
import com.xlh.enums.conclude.LikedSysFunkEnum;
import com.xlh.enums.user.UserStatusEnum;
import com.xlh.exception.common.GlobalException;
import com.xlh.pojo.conclude.BusiFullMark;
import com.xlh.pojo.support.Statistics;
import com.xlh.pojo.support.StatisticsExample;
import com.xlh.pojo.user.User;
import com.xlh.service.conclude.ConcludeBusiFullMarkService;
import com.xlh.service.conclude.ConcludeScoreService;
import com.xlh.service.user.ClassService;
import com.xlh.service.user.UserService;
import com.xlh.support.attendance.po.AttendanceListQueryPO;
import com.xlh.support.attendance.po.AttendanceRecordPO;
import com.xlh.support.attendance.po.SettingMarkPO;
import com.xlh.support.attendance.service.AttendanceService;
import com.xlh.support.attendance.vo.AttendanceResultVO;
import com.xlh.support.attendance.vo.AttendanceSettingMarkVO;
import com.xlh.support.attendance.vo.AttendanceVO;
import com.xlh.support.attendance.websocket.message.AttendanceBeginMessage;
import com.xlh.support.attendance.websocket.message.AttendanceSignMessage;
import com.xlh.util.BeanUtil;
import com.xlh.websocket.GlobalWebsocket;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;

import static com.xlh.support.attendance.constant.AttendanceConstant.SAVE_STATISTICS_KEY;
import static com.xlh.support.attendance.constant.AttendanceConstant.SAVE_STATISTICS_KEY_WITH_PARAM;

/**
 * @author cheer
 */
@Service
public class AttendanceServiceImpl implements AttendanceService {

    @Autowired
    private ClassService classService;

    @Autowired
    private UserService userService;

    @Autowired
    private StatisticsMapper statisticsMapper;

    @Autowired
    private UserMapperExt userMapperExt;

    @Autowired
    private BusiFullMarkMapperHelper busiFullMarkMapperHelper;

    @Autowired
    private ConcludeScoreService concludeScoreService;

    @Autowired
    private ConcludeBusiFullMarkService concludeBusiFullMarkService;

    @Autowired
    private StatisticsMapperExt statisticsMapperExt;

    /**
     * 保存签到过的学生 userId classId
     */
    private SetMultimap<Long, Long> recordedStudentMap = Multimaps.synchronizedSetMultimap(HashMultimap.create());

    /**
     * 保存未签到的学生 userId classId
     */
    private SetMultimap<Long, Long> recordingStudentMap = Multimaps.synchronizedSetMultimap(HashMultimap.create());

    /**
     * 保存开始考勤的老师 userId classId
     */
    private SetMultimap<Long, Long> recordingTeacherMap = Multimaps.synchronizedSetMultimap(HashMultimap.create());

    @Override
    public List<AttendanceVO> listAttendanceStudents(AttendanceListQueryPO attendanceListQueryPO, Boolean isStatisticPage) {
        List<ClassCourseDTO> classList = classService.listClassCourse(
                Lists.newArrayList(attendanceListQueryPO.getCourseId()), attendanceListQueryPO.getStatus(),
                null, null, attendanceListQueryPO.getClassIdList());
        if (CollectionUtils.isEmpty(classList)) {
            throw new GlobalException("班级不存在");
        }

        Map<Long, ClassCourseDTO> classMap = new HashMap<>();
        for (ClassCourseDTO classCourseDTO : classList) {
            classMap.put(classCourseDTO.getId(), classCourseDTO);
        }

        List<UserInfoDTO> userList = userService.queryClassUser(attendanceListQueryPO.getUserInfo(),
                new ArrayList<>(classMap.keySet()), Lists.newArrayList(UserStatusEnum.NOT_DELETE.getCode()),
                false);
        if (isStatisticPage) {
            List<UserInfoDTO> userInfoDTOS = userMapperExt.listClassUserDelete(attendanceListQueryPO.getUserInfo(),
                    null, new ArrayList<>(attendanceListQueryPO.getClassIdList()));
            userList.addAll(userInfoDTOS);
        }

        List<AttendanceVO> attendanceVOs = listAttendance(attendanceListQueryPO.getCourseId(), classMap, userList);

        if (Objects.equals(attendanceListQueryPO.getSort(), "asc")) {
            attendanceVOs.sort(Comparator.comparingInt(AttendanceVO::getAbsentCount));
        } else if (Objects.equals(attendanceListQueryPO.getSort(), "desc")) {
            attendanceVOs.sort(Comparator.comparingInt(AttendanceVO::getAbsentCount).reversed());
        }
        return attendanceVOs;
    }

    @Override
    @PreAcquireLock(value = SAVE_STATISTICS_KEY_WITH_PARAM)
    public BusiFullMarkDTO recordAbsent(AttendanceRecordPO attendanceRecordPO) {
        StatisticsExample example = new StatisticsExample();
        example.or().andCourseIdEqualTo(attendanceRecordPO.getCourseId())
                .andClassIdEqualTo(attendanceRecordPO.getClassId())
                .andUserIdEqualTo(attendanceRecordPO.getUserId());
        List<Statistics> statisticsList = statisticsMapper.selectByExample(example);

        if (CollectionUtils.isNotEmpty(statisticsList)) {
            Statistics statistics = statisticsList.get(0);
            Statistics updateStatistics = new Statistics();
            updateStatistics.setId(statistics.getId());
            updateStatistics.setTruantNo(attendanceRecordPO.getAbsentCount());
            statisticsMapper.updateByPrimaryKeySelective(updateStatistics);
        } else {
            Statistics statistics = new Statistics();
            statistics.setCourseId(attendanceRecordPO.getCourseId());
            statistics.setClassId(attendanceRecordPO.getClassId());
            statistics.setUserId(attendanceRecordPO.getUserId());
            statistics.setTruantNo(attendanceRecordPO.getAbsentCount());
            statisticsMapper.insertSelective(statistics);
        }

        // 回传给页面显示
        BusiFullMarkDTO scoreDTO = computeScore(attendanceRecordPO.getCourseId(), attendanceRecordPO.getClassId(), attendanceRecordPO.getAbsentCount());
        scoreDTO.setUserId(attendanceRecordPO.getUserId());

        return scoreDTO;
    }



    @Override
    public void beginRecord(Long userId, List<Long> classIds) {
        for (Long classId : classIds) {
            if (recordingTeacherMap.containsValue(classId)) {
                throw new GlobalException("选择的班级已经开始签到");
            }
        }

        Set<Long> studentIds = new HashSet<>();
        for (Long classId : classIds) {
            List<Long> userIds = userService.getClassUser(classId);
            if (CollectionUtils.isNotEmpty(userIds)) {
                for (Long studentId : userIds) {
                    studentIds.add(studentId);
                    recordingStudentMap.put(studentId, classId);
                }
            }
            recordingTeacherMap.put(userId, classId);
        }

        AttendanceBeginMessage attendanceBeginMessage = AttendanceBeginMessage.builder()
                .userId(userId)
                .build();
        GlobalWebsocket.sendMessage(new ArrayList<>(studentIds), attendanceBeginMessage);
    }

    @Override
    public void record(Long userId) {
        Set<Long> classIdSet = recordingStudentMap.get(userId);
        if (CollectionUtils.isEmpty(classIdSet)) {
            throw new GlobalException("签到已结束");
        }
        recordedStudentMap.putAll(userId, classIdSet);
        recordingStudentMap.removeAll(userId);

        AttendanceSignMessage attendanceSignMessage = AttendanceSignMessage.builder()
                .userId(userId)
                .build();
        GlobalWebsocket.sendMessage(userId, attendanceSignMessage);
    }

    @Override
    public AttendanceResultVO endRecord(Long userId, Long courseId, List<Long> classIds) {
        if (courseId == null && CollectionUtils.isEmpty(classIds)) {
            return null;
        }

        int recordedCount = 0;
        int unrecordedCount = 0;
        List<String> recordedNames = new ArrayList<>();
        List<String> unrecordedNames = new ArrayList<>();

        for (Long classId : classIds) {
            if (recordingTeacherMap.containsEntry(userId, classId)) {
                List<Long> userIds = userService.getClassUser(classId);
                List<User> users = userService.listUser(userIds);
                if (CollectionUtils.isNotEmpty(users)) {
                    for (User user : users) {
                        if (recordedStudentMap.containsEntry(user.getId(), classId)) {
                            recordedCount++;
                            recordedNames.add(user.getName());
                        } else {
                            unrecordedCount++;
                            unrecordedNames.add(user.getName());
                            ((AttendanceService) (AopContext.currentProxy())).addAbsent(courseId, classId, user.getId());
                        }
                        recordedStudentMap.remove(user.getId(), classId);
                        recordingStudentMap.remove(user.getId(), classId);
                    }
                }
                recordingTeacherMap.remove(userId, classId);
            }
        }

        return AttendanceResultVO.builder()
                .recordedCount(recordedCount)
                .unrecordedCount(unrecordedCount)
                .recordedNames(recordedNames)
                .unrecordedNames(unrecordedNames)
                .build();
    }

    @Override
    public List<Long> currentAttendance(Long userId) {
        Set<Long> classIdSet = recordingTeacherMap.get(userId);
        if (CollectionUtils.isEmpty(classIdSet)) {
            return new ArrayList<>();
        }
        return new ArrayList<>(classIdSet);
    }

    @PreAcquireLock(SAVE_STATISTICS_KEY)
    @Override
    public void addAbsent(Long courseId, Long classId, Long userId) {
        StatisticsExample example = new StatisticsExample();
        example.or().andCourseIdEqualTo(courseId)
                .andClassIdEqualTo(classId)
                .andUserIdEqualTo(userId);
        List<Statistics> statisticsList = statisticsMapper.selectByExample(example);

        int truantNo;
        if (CollectionUtils.isNotEmpty(statisticsList)) {
            Statistics statistics = statisticsList.get(0);
            if (statistics.getTruantNo() == null) {
                truantNo = 1;
            } else {
                truantNo = statistics.getTruantNo() + 1;
            }
            if (truantNo <= 999) {
                Statistics updateStatistics = new Statistics();
                updateStatistics.setId(statistics.getId());
                updateStatistics.setTruantNo(truantNo);
                statisticsMapper.updateByPrimaryKeySelective(updateStatistics);
            }
        } else {
            Statistics statistics = new Statistics();
            statistics.setCourseId(courseId);
            statistics.setClassId(classId);
            statistics.setUserId(userId);
            statistics.setTruantNo(1);
            statisticsMapper.insertSelective(statistics);
        }
    }

    @Override
    public Boolean isAttendanceRunning(Long userId) {
        return recordingStudentMap.containsKey(userId);
    }

    @Override
    public List<Statistics> findAttendanceByUserIds(Long courseId, Long classId, List<Long> userIds) {

        StatisticsExample selectByExample = new StatisticsExample();
        selectByExample.createCriteria().andClassIdEqualTo(classId).andCourseIdEqualTo(courseId).andUserIdIn(userIds);
        List<Statistics> statistics = statisticsMapper.selectByExample(selectByExample);
        outer:
        for (Long userId : userIds) {
            for (Statistics statistic : statistics) {
                Long classIdTemp = statistic.getClassId();
                Long courseIdTemp = statistic.getCourseId();
                Long userIdTemp = statistic.getUserId();
                if(classIdTemp.equals(classId)&& courseIdTemp.equals(courseId)&& userIdTemp.equals(userId)){
                   continue outer;
                }
            }
            Statistics newStatistics = new Statistics();
            newStatistics.setCourseId(courseId).setClassId(classId).setUserId(userId);
            statistics.add(newStatistics);
        }
        return statistics;
    }


    private List<AttendanceVO> listAttendance(Long courseId, Map<Long, ClassCourseDTO> classMap,
                                              List<UserInfoDTO> userList) {
        Table<Long, Long, Statistics> statisticsTable = HashBasedTable.create();
        if (MapUtils.isNotEmpty(classMap)) {
            StatisticsExample example = new StatisticsExample();
            example.or().andClassIdIn(new ArrayList<>(classMap.keySet()))
                    .andCourseIdEqualTo(courseId);
            List<Statistics> statisticsList = statisticsMapper.selectByExample(example);

            for (Statistics statistics : statisticsList) {
                statisticsTable.put(statistics.getUserId(), statistics.getClassId(), statistics);
            }
        }

        List<AttendanceVO> attendanceVOs = new ArrayList<>();
        for (UserInfoDTO user : userList) {
            AttendanceVO attendanceVO =  AttendanceVO.builder()
                    .userId(user.getId())
                    .username(user.getName())
                    .userNumber(user.getUserNumber())
                    .classId(user.getClassId())
                    .build();

            ClassCourseDTO classCourseDTO = classMap.get(user.getClassId());
            if (classCourseDTO != null) {
                attendanceVO.setClassName(classCourseDTO.getName());
                attendanceVO.setGrade(classCourseDTO.getGrade());
            }
            if (statisticsTable.contains(user.getId(), user.getClassId())) {

                attendanceVO.setAbsentCount(statisticsTable.get(user.getId(), user.getClassId()).getTruantNo());
            } else {
                attendanceVO.setAbsentCount(0);
            }
            BusiFullMarkDTO scoreDTO = computeScore(courseId, user.getClassId(), attendanceVO.getAbsentCount());
            attendanceVO.setScore(scoreDTO.getScore());

            attendanceVOs.add(attendanceVO);
        }
        return attendanceVOs;
    }


    /**
     * 计算分数
     * @param courseId 课程ID
     * @param classId   班级ID
     * @param truantNo  旷课次数
     * @return
     */
    private BusiFullMarkDTO computeScore(Long courseId, Long classId, Integer truantNo) {
        // 获取满分设置进行动态计算
        String sysFunkKey = LikedSysFunkEnum.CHECKING_IN.getSysFunkKey();
        BusiFullMark busiFullMark = busiFullMarkMapperHelper.get(sysFunkKey, courseId, classId, null);

        return computeScore(truantNo, busiFullMark);
    }

    private BusiFullMarkDTO computeScore(Integer truantNo, BusiFullMark busiFullMark) {

        Integer score = null;
        BusiFullMarkDTO scoreDTO = BeanUtil.transform( BusiFullMarkDTO.class, busiFullMark );
        if(ObjectUtil.isNotNull(busiFullMark.getDegression())){

            BigDecimal residue = NumberUtil.mul(truantNo, busiFullMark.getDegression());
            if(busiFullMark.getFullMark().compareTo(residue.intValue()) == -1){
                score = 0;
            }else{
                BigDecimal sub = NumberUtil.sub(busiFullMark.getFullMark(), residue);
                score = sub.intValue();
            }
        }
        scoreDTO.setScore(score);

        return scoreDTO;
    }

    @Transactional
    @Override
    public void settingFullMark(SettingMarkPO settingMarkPO) {

        // 扣减分数校验
        if(ObjectUtil.isNull(settingMarkPO.getDegression())){
            throw new GlobalException(ResultEnum.ERROR_COMPARE_MANIFESTSCORE);
        }
        if(0 >= settingMarkPO.getDegression()){
            throw new GlobalException(ResultEnum.ERROR_COMPARE_MORETHANZERO);
        }
        if(settingMarkPO.getDegression().compareTo(settingMarkPO.getFullMark()) == 1){
            throw new GlobalException(ResultEnum.ERROR_COMPARE_FULL_MARK);
        }

        // 设置业务类型
        settingMarkPO.setBusiType(LikedSysFunkEnum.CHECKING_IN.getSysFunkKey());
        concludeBusiFullMarkService.settingFullMark(settingMarkPO , null);

    }


    @Override
    public AttendanceSettingMarkVO getFullMarkSetting(Long classId, Long courseId) {

        BusiFullMark busiFullMark = busiFullMarkMapperHelper.get(LikedSysFunkEnum.CHECKING_IN.getSysFunkKey(), courseId, classId, null);
        AttendanceSettingMarkVO attendanceSettingMarkVO = BeanUtil.transform(AttendanceSettingMarkVO.class, busiFullMark);

        return attendanceSettingMarkVO;
    }




}
